package io;


import java.io.File;
import java.io.FileInputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.zip.GZIPInputStream;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Parses an GPX File 
 * 
 * @author Oliver Duerr
 */
public class GPXFileParser {
	
	public static List<GPSPoint> parseFile(final File file) throws Exception {
		if (!file.exists()) {
			throw new IllegalArgumentException("gpx-log-file '" + file.getAbsolutePath() + "' does not exist.");
		}
		SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
		List<GPSPoint> ret = new ArrayList<GPSPoint>();
		DefaultHandler handler = new MyContentHandler(ret);
		if (file.getName().toLowerCase().endsWith(".gz")) {
			saxParser.parse(new GZIPInputStream(new FileInputStream(file)), handler);
		} else {
			saxParser.parse(file, handler);
		}
		return ret;
	}
	
	public static void main(String[] args) throws Exception {
		File file = new File("data/2011-01-15 15_42_22.gpx");
		List<GPSPoint> points = parseFile(file);
		System.out.println("read " + points.size() + " tracks");
		long lastTime = 0;
		for (GPSPoint gpsPoint : points) {
			
		}
	}
	
	
	 /**
     * Helper-class to parse the XML using SAX.
     *  @author <a href="mailto:Marcus@Wolschon.biz">Marcus Wolschon</a>
     */
    private static final class MyContentHandler extends DefaultHandler {

        /**
         * Buffer for XML elements values.
         */
        private StringBuffer buffer = new StringBuffer();

        /**
         * Context XML path for current element value.
         */
        private Stack<String> context = new Stack<String>();

        /**
         * Temporary variables for parsing.
         */
        private double lat, lon, altitude, course, speed;

        /**
         * Temporary variables for parsing.
         */
        private long time;

        /**
         * Flags presents source .gpx file have an speed and cog attributes.
         * Else its calculated.
         */
        private boolean hasSpeed, hasCourse;

        /**
         * Previous values needs to calculate speed and course.
         */
        private long prevTime;

        /**
         * Previous values needs to calculate speed and course.
         */
        private double prevLat, prevLon;

        /**
         * Previous values needs to calculate speed and course.
         */
        private double speedLat, speedLon, prevSpeed, prevCourse;

        /**
         * Fix counter for average speed calculation.
         */
        private int fixCount;

		private final List<GPSPoint> gpsList;

		private double heartRate;

        public MyContentHandler(List<GPSPoint> ret) {
			this.gpsList = ret;
		}

		/**
         * ${@inheritDoc}.
         */
        @Override
        public void startElement(final String namespaceURI,
                                 final String localName,   // local name
                                 final String qName,       // qualified name
                                 final Attributes attrs) throws SAXException {
            try {
                this.buffer.setLength(0);
                this.context.push(qName);
                // tracks delimiter for various segment
                if (qName.equals("trkpt")) {
                    lat = Double.parseDouble(attrs.getValue("lat"));
                    lon = Double.parseDouble(attrs.getValue("lon"));
                }
            } catch (Exception e) {
                System.err.println("Exception while reading GPX-log. skipping.");
            }
        }

        /**
         * Parse trackpoint elements.
         * <ele></ele>
         * <time></time>
         * ...
         * @param elementPath the full qualified name for given element (e.g. "/gpx/trk/trkseg/trkpt/time")
         * @param elementValue string value for given element (e.g. "2009-01-01T14:27:53Z")
         */
        private void parseTrackpointElement(final String elementPath, final String elementValue) {
            if (elementPath.equals("/gpx/trk/trkseg/trkpt/time")) {
                final int dateEnd = 10;
                final int timeEnd = 19;
                time = parseTime(elementValue);
            }
            if (elementPath.equals("/gpx/trk/trkseg/trkpt/ele")) {
                altitude = parseDouble(elementValue);
            }
            if (elementPath.equals("/gpx/trk/trkseg/trkpt/speed")) {
                if (!hasSpeed) {
                    hasSpeed = true;
                }
                speed = parseDouble(elementValue);
                final double speedFactor = 1.944d;
            }
            if (elementPath.equals("/gpx/trk/trkseg/trkpt/extensions/tp1:TrackPointExtension/tp1:hr")) {
            	heartRate = parseDouble(elementValue);
            }
            if (elementPath.equals("/gpx/trk/trkseg/trkpt/cog") || elementPath.equals("/gpx/trk/trkseg/trkpt/course")) {
                if (!hasCourse) {
                    hasCourse = true;
                }
                course = parseDouble(elementValue);
            }
        }

        
        private long parseTime(final String aTimeStr) {
        	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
            try {
                return (sdf.parse(aTimeStr)).getTime() + 1000 * 60 * 60 * 3;//TODO fix the adding of 3 h
            } catch (ParseException e) {
               System.err.println("Error in GPX date parsing : " + aTimeStr);
            }
            return 0;
        }
        
        /**
         * Convert string representation of gps to double
         * 1. horizontal, vertical, position dilution of precision
         * 2. altitude value
         * @param strDouble string value
         * @return double parsed value
         */
        private double parseDouble(final String strDouble) {
            return Double.parseDouble(strDouble);
        }

        /**
         * Invokes at the end of each xml element.
         * @param uri the namespace
         * @param local the local name of the element
         * @param qName the name of the element
         * @throws SAXException if we cannot parse
         */
        public void endElement(final String uri, final String local, final String qName)
                throws SAXException {
            this.flushText();
            this.context.pop();
            try {
                // tracks delimiter for various segment
                if (qName.equals("trkseg")) {
//                    System.out.println("seg-end");
                }
                // end of one trackpoint
                if (qName.equals("trkpt")) {
                    gpsList.add(new GPSPoint(time, lon, lat, altitude, (int) heartRate));
                }
            } catch (Exception e) {
               e.printStackTrace();
            }
        }

        public void characters(final char[] ch, final int start, final int length)
                throws SAXException {
            this.buffer.append(ch, start, length);
        }

        /**
         * Clear XML buffer and execute gpx elements value parser.
         */
        protected void flushText() {
            if (this.buffer.length() > 0) {
                StringBuffer path = new StringBuffer();
                for (String pathElement : this.context) {
                    path.append("/" + pathElement);
                }
                parseTrackpointElement(path.toString(), this.buffer.toString());
            }
            this.buffer.setLength(0);
         }


    }

}
